#include "filesystemdata.h"

#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include "filehandle.h"
#include "folder.h"

enum bitToSet_t{
	BIT_ISOCCUIPED,
	BIT_ISHS
};


/// Given a block and a position in /4 of bytes, it returns RET_TRUE if the
/// bit corresponding to the property whereToGet is set.
static fs_ret_t fs_getBitInBlockMap(byte_t *block,uint32_t bitToGet, enum bitToSet_t whereToGet){
	// Bithack powaaaaa !
	fs_ret_t ret;
	if(whereToGet==BIT_ISHS){
		ret = ((block[bitToGet/4])&((0x02<<((bitToGet%4)*2))));
	}else{
		ret = ((block[bitToGet/4])&((0x01<<((bitToGet%4)*2))));
	}
	return ret?RET_TRUE:RET_FALSE;
}

/// Given a block and a position in /4 of bytes, it sets the bit corresponding
/// to whereToSet to valToSet.
static fs_ret_t fs_setBitInBlockMap(byte_t *block,uint32_t bitToSet, enum bitToSet_t whereToSet,uint8_t valToSet){
	// Bithack powaaaaa !
	if(valToSet){
		if(whereToSet==BIT_ISHS){
			block[bitToSet/4] = ((block[bitToSet/4])|(0x02<<((bitToSet%4)*2)));
		}else{
			block[bitToSet/4] = ((block[bitToSet/4])|(0x01<<((bitToSet%4)*2)));
		}
	}else{
		if(whereToSet==BIT_ISHS){
			block[bitToSet/4] = ((block[bitToSet/4])&(~(0x02<<((bitToSet%4)*2))));
		}else{
			block[bitToSet/4] = ((block[bitToSet/4])&(~(0x01<<((bitToSet%4)*2))));
		}
	}
	return RET_NO_PROBLEM;
}

/// Generates the partition metadata into fsd, and writes it to the disk.
static fs_ret_t fs_format_genFileSystemData(HardDriveInterface_t* hdi, FileSystemData_t *fsd,byte_t * const block){
	// Create the root data of the partition.
	// C auto-fill with zeroes. This behaviour is needed.
	static const char partitionFormat[PARTITIONFORMATSTRINGLEN] = {'b','a','b','o','o','n','\0'};
	if(sizeof(FileSystemData_t)>hdi->blockSize||hdi->blockSize<64){
		return ERR_PARAMETER_PROBLEM;
	}
	memcpy(fsd->partitionFormat,partitionFormat,PARTITIONFORMATSTRINGLEN);
	fsd->version = FILESYSTEMVERSION;
	fsd->blockCount = hdi->blockCount;
	fsd->blockSize = hdi->blockSize;
	fsd->bitmap = FSBYTEMAPBLOCK;
	memcpy(block,fsd,sizeof(FileSystemData_t));
	fs_setBlockToHdi(hdi,FSDBLOCK,block);
	return RET_NO_PROBLEM;
}

/// Generates the bitmap telling for each block of the disk whether it is
/// occupied or not, and whether the block is usable or not.
static fs_ret_t fs_format_genBlockMap(HardDriveInterface_t* hdi, FileSystemData_t *fsd,byte_t * const block){
	uint32_t currentBytemapBlock,posInBlock;
	uint32_t bpBlock = FSBITMAP_BLOCKINFOPERBLOCK(*fsd);
	uint32_t nBlocksToUse = (fsd->blockCount-1)/bpBlock+1;
	for(currentBytemapBlock=0;currentBytemapBlock<nBlocksToUse;++currentBytemapBlock){
		blockid_t nextBlock = currentBytemapBlock+1+FSBYTEMAPBLOCK;
		memset(block,0x00,fsd->blockSize);
		for(posInBlock=0;posInBlock<bpBlock;++posInBlock){
			uint32_t currentBlockToWrite = bpBlock*currentBytemapBlock+posInBlock;
			uint8_t isFull = 0,isHS = 0;// By default, the sector works.
			if(currentBlockToWrite==FSDBLOCK
				||(currentBlockToWrite>=FSBYTEMAPBLOCK
						&& currentBlockToWrite<FSBYTEMAPBLOCK+nBlocksToUse)){
				isFull = 1;
			}
			fs_setBitInBlockMap(block,posInBlock,BIT_ISOCCUIPED,isFull);
			fs_setBitInBlockMap(block,posInBlock,BIT_ISHS,isHS);
		}
		memcpy(block+posInBlock/4,&nextBlock,sizeof(uint32_t));
		fs_setBlockToHdi(hdi,currentBytemapBlock+FSBYTEMAPBLOCK, block);
	}
	return RET_NO_PROBLEM;
}

fs_ret_t fs_format(HardDriveInterface_t* hdi){
	byte_t *block = malloc(hdi->blockSize);
	FileSystemData_t fsd;
	fs_ret_t ret;
	if(!block) return ERR_OUT_OF_MEMORY;
	ret = fs_format_genFileSystemData(hdi,&fsd,block);
	if(ret) goto cleanup;
	//Allocating the bytemap
	ret = fs_format_genBlockMap(hdi,&fsd,block);
	if(ret) goto cleanup;
	{
		FileSystemHandle_t fsh;
		ret = fs_openFileSystem(hdi, &fsh);
		if(ret) goto cleanup;
		{
			inodeid_t rootInodeId;
			// The second parameter is given with this value to change the behaviour easily.
			ret = fs_newFolder(&fsh,rootInodeId,&rootInodeId,true,false);
			if(ret) goto cleanup;
			ret = fs_setFileSystemRootPos(&fsh, rootInodeId);
			if(ret) goto cleanup;
		}
		ret = fs_closeFileSystem(&fsh);
		if(ret) goto cleanup;
	}
	cleanup:
	free(block);
	return ret;
}

fs_ret_t fs_openFileSystem(HardDriveInterface_t* hdi, FileSystemHandle_t *fsh){
	byte_t *block = malloc(hdi->blockSize);
	int i;
	if(!block) return ERR_OUT_OF_MEMORY;
	// TODO : Test return
	fs_getBlockFromHdi(hdi,FSDBLOCK, block);
	memcpy(&(fsh->fsd),block,sizeof(FileSystemData_t));
	fsh->hdi = hdi;
	// TODO : Add a flag telling that the file-system is opened.
	fsh->nAllocatedFileHandles = FS_FILE_HANDLES_ALLOCATED_DEFAULT;
	fsh->openedFilesHandles = malloc(FS_FILE_HANDLES_ALLOCATED_DEFAULT*sizeof(FileHandle_t));
	if(!fsh->openedFilesHandles){
		free(block);
		return ERR_OUT_OF_MEMORY;
	}
	for(i=0;i<FS_FILE_HANDLES_ALLOCATED_DEFAULT;++i){
		fsh->openedFilesHandles[i].isEmpty = true;
	}
	free(block);
	return RET_NO_PROBLEM;
}

fs_ret_t fs_closeFileSystem(FileSystemHandle_t *fsh){
	// TODO : remove the 'opened' flag.
	free(fsh->openedFilesHandles);
	return RET_NO_PROBLEM;
}

fs_ret_t fs_setFileSystemRootPos(FileSystemHandle_t *fsh, blockid_t pos){
	byte_t *block = malloc(fsh->hdi->blockSize);
	fs_ret_t ret;
	if(!block) return ERR_OUT_OF_MEMORY;
	fsh->fsd.fsRoot = pos;
	memcpy(block,&(fsh->fsd),sizeof(FileSystemData_t));
	ret = fs_setBlockToHdi(fsh->hdi,FSDBLOCK,block);
	free(block);
	return ret;
}

fs_ret_t fs_getIsBlockOccuiped(FileSystemHandle_t *fsh,blockid_t blockToCheck){
	byte_t *block = malloc(fsh->fsd.blockSize);
	blockid_t currentBytemapBlock = fsh->fsd.bitmap;
	fs_ret_t ret = RET_NO_PROBLEM;
	uint32_t blockPassed;
	uint32_t bitToCheck = 0;
	if(!block) return ERR_OUT_OF_MEMORY;
	if(blockToCheck > fsh->fsd.blockCount){
		ret = ERR_OUT_OF_RANGE;
		goto cleanup;
	}
	for(blockPassed=0;blockPassed<(blockToCheck)/FSBITMAP_BLOCKINFOPERBLOCK(fsh->fsd);++blockPassed){
		fs_getBlockFromHdi(fsh->hdi,currentBytemapBlock, block);
		memcpy(&currentBytemapBlock,block+fsh->fsd.blockSize-sizeof(blockid_t),sizeof(blockid_t));
	}
	fs_getBlockFromHdi(fsh->hdi,currentBytemapBlock, block);
	bitToCheck = blockToCheck-blockPassed*FSBITMAP_BLOCKINFOPERBLOCK(fsh->fsd);
	ret = fs_getBitInBlockMap(block,bitToCheck, BIT_ISOCCUIPED)
		| fs_getBitInBlockMap(block,bitToCheck, BIT_ISHS);
	cleanup:
	free(block);
	return ret?RET_TRUE:ret;
}

fs_ret_t fs_setBlockToOccuiped(FileSystemHandle_t *fsh,blockid_t blockToSet){
	byte_t *block = malloc(fsh->fsd.blockSize);
	blockid_t currentBytemapBlock = fsh->fsd.bitmap;
	uint32_t blocksPassed;
	uint32_t bitToSet = 0;
	fs_ret_t ret=RET_NO_PROBLEM;
	if(!block) return ERR_OUT_OF_MEMORY;
	if(blockToSet > fsh->fsd.blockCount){// Out of range.
		ret = ERR_OUT_OF_RANGE;
		goto cleanup;
	}
	for(blocksPassed=0;blocksPassed<(blockToSet)/FSBITMAP_BLOCKINFOPERBLOCK(fsh->fsd);++blocksPassed){
		fs_getBlockFromHdi(fsh->hdi,currentBytemapBlock, block);
		memcpy(&currentBytemapBlock,block+fsh->fsd.blockSize-sizeof(blockid_t),sizeof(blockid_t));
	}
	fs_getBlockFromHdi(fsh->hdi,currentBytemapBlock, block);
	bitToSet = blockToSet-blocksPassed*FSBITMAP_BLOCKINFOPERBLOCK(fsh->fsd);
	if(fs_getBitInBlockMap(block,bitToSet, BIT_ISOCCUIPED)){// Block Already set
		WARNING_PRINT("Block already set : %d\n",blockToSet);
		ret = ERR_ALREADY_DONE;
	}
	if(fs_getBitInBlockMap(block,bitToSet, BIT_ISHS)){// Block HS
		WARNING_PRINT("Block HS : %d\n",blockToSet);
		ret = ERR_CANT_DO_IT_ALWAYS;
	}
	if(!ret){
		fs_setBitInBlockMap(block,bitToSet,BIT_ISOCCUIPED,1);
		fs_setBlockToHdi(fsh->hdi,currentBytemapBlock, block);
	}
	cleanup:
	free(block);
	return ret;
}

fs_ret_t fs_freeBlock(FileSystemHandle_t *fsh,blockid_t blockToFree){
	byte_t *block = malloc(fsh->fsd.blockSize);
	blockid_t currentBytemapBlock = fsh->fsd.bitmap;
	uint32_t blockPassed;
	uint32_t bitToFree = 0;
	fs_ret_t ret=RET_NO_PROBLEM;
	if(!block) return ERR_OUT_OF_MEMORY;
	if(blockToFree > fsh->fsd.blockCount){
		ret = ERR_OUT_OF_RANGE;
		goto cleanup;
	}
	for(blockPassed=0;blockPassed<(blockToFree)/FSBITMAP_BLOCKINFOPERBLOCK(fsh->fsd);++blockPassed){
		fs_getBlockFromHdi(fsh->hdi,currentBytemapBlock, block);
		memcpy(&currentBytemapBlock,block+fsh->fsd.blockSize-sizeof(blockid_t),sizeof(blockid_t));
	}
	fs_getBlockFromHdi(fsh->hdi,currentBytemapBlock, block);
	bitToFree = blockToFree-blockPassed*FSBITMAP_BLOCKINFOPERBLOCK(fsh->fsd);
	if(fs_getBitInBlockMap(block,bitToFree, BIT_ISHS)){
		WARNING_PRINT("Block HS : %d",blockToFree);
		ret = ERR_CANT_DO_IT_ALWAYS;
		goto cleanup;
	}
	if(!fs_getBitInBlockMap(block,bitToFree, BIT_ISOCCUIPED)){// Block not set.
		WARNING_PRINT("Block already empty : %d\n",blockToFree);
		ret = ERR_ALREADY_DONE;
		goto cleanup;
	}
	fs_setBitInBlockMap(block,bitToFree, BIT_ISOCCUIPED,0);
	fs_setBlockToHdi(fsh->hdi,currentBytemapBlock, block);
	cleanup:
	free(block);
	return ret;
}

fs_ret_t fs_bookAnyEmptyBlock(FileSystemHandle_t *fsh,blockid_t *blockSelected){
	blockid_t blockToTest = FSBYTEMAPBLOCK+fsh->fsd.blockCount/FSBITMAP_BLOCKINFOPERBLOCK(fsh->fsd);
	// Starting after the bytemap. This may change later, for example if the
	// begining of the dynamically used part changes. Could start at 0,
	// this is just a small optimisation.
	for(; blockToTest < fsh->fsd.blockCount; ++blockToTest){
		if(!fs_getIsBlockOccuiped(fsh,blockToTest)){
			fs_ret_t ret = fs_setBlockToOccuiped(fsh,blockToTest);
			*blockSelected = blockToTest;
			return ret;
		}
	}
	return ERR_CANT_DO_IT_NOW;// Not found, disk full.
}

fs_ret_t fs_diskUsage(FileSystemHandle_t *fsh, uint32_t *blocksOccuipedCount){
	uint32_t i;
	*blocksOccuipedCount = 0;
	for(i=0;i<fsh->fsd.blockCount;i++){
		if(fs_getIsBlockOccuiped(fsh,i)){
			++(*blocksOccuipedCount);
		}
	}
    return RET_NO_PROBLEM;
}

